package com.yg.poseavatar;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;

import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.hardware.Camera.CameraInfo;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.List;


public class PoseEstimateFragment extends android.app.Fragment implements Camera.PreviewCallback {
    private static final String TAG = "PoseEstimateFragment";
    private static final Size DESIRED_PREVIEW_SIZE = new Size(1280,720);
    private static final Logger LOGGER = new Logger();
    /** Conversion from screen rotation to JPEG orientation. */
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    private static final String HANDLE_THREAD_NAME = "CameraBackground";


    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    private Camera camera;
    private Size desiredSize = DESIRED_PREVIEW_SIZE;
    /** The layout identifier to inflate for this Fragment. */
    private int layout = R.id.container;
    /** An {@link AutoFitTextureView} for camera preview. */
    private AutoFitTextureView textureView;
    private MyDrawView drawView;
    private TextView textView;
    private Button switchCamButton;
    private Boolean isMirrorEnabled = false;
    private GreetClient client;

    /**
     * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a {@link
     * TextureView}.
     */

    // For pose estimates

    private ImageClassifier classifier;
    private final Object lock = new Object();
    private boolean runClassifier = false;
    /** An additional thread for running tasks that shouldn't block the UI. */
    private HandlerThread backgroundThread;
    private HandlerThread wsThread;

    /** A {@link Handler} for running tasks in the background. */
    private Handler backgroundHandler;
    private Handler wsHandeler;

    private String SERVER_IP = "192.168.31.207";

    private int PORT = 8765;


    public static PoseEstimateFragment newInstance() {
        return new PoseEstimateFragment(R.id.container, DESIRED_PREVIEW_SIZE);
    }

    private final TextureView.SurfaceTextureListener surfaceTextureListener =
            new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(
                        final SurfaceTexture texture, final int width, final int height) {

                    int index = getCameraId();
                    camera = Camera.open(index);
                    DisplayMetrics metrics = new DisplayMetrics();
                    getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
                    float yPixels = metrics.heightPixels;
                    float xPixels = metrics.widthPixels;

                    try {
                        Camera.Parameters parameters = camera.getParameters();
                        List<String> focusModes = parameters.getSupportedFocusModes();
                        if (focusModes != null
                                && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                        }
                        List<Camera.Size> cameraSizes = parameters.getSupportedPreviewSizes();
                        Size[] sizes = new Size[cameraSizes.size()];
                        int i = 0;
                        for (Camera.Size size : cameraSizes) {
                            sizes[i++] = new Size(size.width, size.height);
                        }
                        Size previewSize =
                                CameraConnectionFragment.chooseOptimalSize(
                                        sizes, desiredSize.getWidth(), desiredSize.getHeight());
                        parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
                        //camera.setDisplayOrientation(90);
                        int orientation = getResources().getConfiguration().orientation;
                        if (orientation == Configuration.ORIENTATION_LANDSCAPE){
                            camera.setDisplayOrientation(0);
                        }
                        else{
                            camera.setDisplayOrientation(90);
                        }
                        camera.setParameters(parameters);
                        camera.setPreviewTexture(texture);
                    } catch (IOException exception) {
                        camera.release();
                    }

//                    camera.setPreviewCallbackWithBuffer(imageListener);
                    Camera.Size s = camera.getParameters().getPreviewSize();
                    camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.height, s.width)]);
                    //textureView.setAspectRatio(s.width, s.height);
                    textureView.setAspectRatio(Math.round(yPixels), Math.round(xPixels));

                    camera.startPreview();
                }

                @Override
                public void onSurfaceTextureSizeChanged(
                        final SurfaceTexture texture, final int width, final int height) {}

                @Override
                public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
                    return true;
                }

                @Override
                public void onSurfaceTextureUpdated(final SurfaceTexture texture) {}
            };

    @SuppressLint("ValidFragment")
    public PoseEstimateFragment(final int layout, final Size desiredSize) {
        this.layout = layout;
        this.desiredSize = desiredSize;
    }

    public PoseEstimateFragment() {
    }

    @Override
    public View onCreateView(
            final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_pose_estimate, container, false);
    }

    @Override
    public void onViewCreated(final View view, final Bundle savedInstanceState) {
        textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
        drawView = (MyDrawView)view.findViewById(R.id.drawPoint);
        textView = (TextView) view.findViewById(R.id.text);
        switchCamButton = (Button) view.findViewById(R.id.switchCamera);
        switchCamButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                textView.setText("Switch cam button clicked");
                isMirrorEnabled = !isMirrorEnabled;
                textureView.setScaleX(isMirrorEnabled ? -1:1);
            }
        });
        if (hasPermission()){
            Log.i("Permission", "Camera permission gained");
        }
        else{
            requestPermission();
        }
        if (textureView.isAvailable()) {
            camera.startPreview();
        } else {
            textureView.setSurfaceTextureListener(surfaceTextureListener);
        }
        backgroundThread = new HandlerThread(HANDLE_THREAD_NAME);
        backgroundThread.start();
        backgroundHandler = new Handler(backgroundThread.getLooper());
        synchronized (lock) {
            runClassifier = true;
        }
        backgroundHandler.post(initClassify);
        startBackgroundThread();
        wsThread = new HandlerThread("WSThread");
        wsThread.start();
        wsHandeler = new Handler(wsThread.getLooper());

    }

    private void socketSendMessage(String message){
        if (client == null){
            client = new GreetClient();
            client.startConnection(SERVER_IP, PORT);
            client.sendMessage(message);
        }
        else{
            if (client.clientSocket.isConnected() == false){
                client.startConnection(SERVER_IP, PORT);
                client.sendMessage(message);
            }
            else{
                client.sendMessage(message);
            }
        }

    }

    public class GreetClient {
        private Socket clientSocket;
        private PrintWriter out;
        private BufferedReader in;
        public void startConnection(final String ip, final int port) {
            Runnable scRunnable =
                    new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Log.i("Socket", "try to connect");
                                clientSocket = new Socket(ip, port);
                                out = new PrintWriter(clientSocket.getOutputStream(), true);
                                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                            }
                            catch (IOException e){
                                e.printStackTrace();
                            }
                        }
                    };
            if (clientSocket == null){
                    wsHandeler.post(scRunnable);
            }
        }

        public String sendMessage(final String msg) {
            Runnable sendMessageRunnable =
                    new Runnable() {
                        @Override
                        public void run() {
                            out.println(msg);
                        }
                    };
                    wsHandeler.post(sendMessageRunnable);

            return "";
        }

        public void stopConnection() {
            Runnable stopConnectionRunnable =
                    new Runnable() {
                        @Override
                        public void run() {

                            try {
                                in.close();
                            }
                            catch (IOException e){
                                e.printStackTrace();
                            }
                            out.close();
                            try {
                                clientSocket.close();
                            }
                            catch (IOException e){
                                e.printStackTrace();
                            }
                        }
                        };
            if (clientSocket != null){
                if (clientSocket.isConnected()){
                    wsHandeler.post(stopConnectionRunnable);
                }
            }
        }
    }

    private void initClient(){
        wsHandeler.post(initSocketClientRunnable);
    }

    /** Takes photos and classify them periodically. */
    private Runnable singleBGClassify =
            new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        if (runClassifier) {
                            classifyFrame();
                        }
                    }
                }
            };

    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity() == null){
            Toast.makeText(getActivity(), "Empty activity", Toast.LENGTH_SHORT).show();
        }

//        socket.sendOnOpen("open", "");
//        socket.close();

    }



    @Override
    public void onResume() {
        super.onResume();
    }

    private Runnable initSocketClientRunnable =
            new Runnable() {
                @Override
                public void run() {
                    client = new GreetClient();
                }
            };

    /** Takes photos and classify them periodically. */
    private Runnable periodicClassify =
            new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        if (runClassifier) {
                            classifyFrame();
                        }
                    }
                    backgroundHandler.post(periodicClassify);
                }
            };


    private Runnable initClassify =
            new Runnable() {
                @Override
                public void run() {
                    Log.i(TAG, "Trying to init classifier");
                    synchronized (lock) {
                        if (runClassifier) {
                            try {
                                classifier = new ImageClassifier(getActivity());
                            } catch (IOException e) {
                                Toast.makeText(getActivity(), "Error init classifier", Toast.LENGTH_SHORT).show();
                                e.printStackTrace();
                            }
                        }
                    }
                }
            };

    @Override
    public void onPause() {
        stopCamera();
        stopBackgroundThread();
        super.onPause();
    }

    private void startBackgroundThread() {
        synchronized (lock) {
            runClassifier = true;
        }
        backgroundHandler.post(periodicClassify);
    }

    private void stopBackgroundThread() {
        backgroundThread.quitSafely();
        try {
            backgroundThread.join();
            backgroundThread = null;
        } catch (final InterruptedException e) {
            LOGGER.e(e, "Exception!");
        }
    }

    protected void stopCamera() {
        if (camera != null) {
            camera.stopPreview();
            camera.setPreviewCallback(null);
            camera.release();
            camera = null;
        }
    }


    private int getCameraId() {
        CameraInfo ci = new CameraInfo();
        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
            Camera.getCameraInfo(i, ci);
            if (ci.facing == CameraInfo.CAMERA_FACING_BACK) return i;
        }
        return -1; // No camera found
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
            Log.i("Preview", "Preview");
            camera.addCallbackBuffer(data);
    }

    private boolean hasPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String PERMISSION_CAMERA = Manifest.permission.CAMERA;
            return ContextCompat.checkSelfPermission(getActivity(), PERMISSION_CAMERA) == PackageManager.PERMISSION_GRANTED;
        } else {
            return true;
        }
    }


    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                Toast.makeText(
                        getActivity(),
                        "Camera permission is required for this demo",
                        Toast.LENGTH_LONG)
                        .show();
            }
            requestPermissions(new String[] {Manifest.permission.CAMERA}, 1);
        }
    }

    /** Classifies a frame from the preview stream. */
    private void classifyFrame() {
        if (classifier == null || getActivity() == null) {
            if (classifier == null){
                Toast.makeText(getActivity(),"Uninitiated classifier",Toast.LENGTH_LONG).show();
            }
            if (getActivity() == null){
                Toast.makeText(getActivity(),"Null activity",Toast.LENGTH_LONG).show();
            }
//            showToast("Uninitialized Classifier or invalid context.");
            return;
        }
        Bitmap bitmap =
                textureView.getBitmap(ImageClassifier.DIM_IMG_SIZE_X, ImageClassifier.DIM_IMG_SIZE_Y);
        Matrix matrix = new Matrix();
        matrix.preScale(-1.0f, 1.0f);
        Bitmap bFlip = Bitmap.createBitmap(bitmap,0,0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        if (bitmap != null){
            String textToShow;
            if (isMirrorEnabled){
                textToShow = classifier.classifyFrame(bFlip);
            }
            else{
                textToShow = classifier.classifyFrame(bitmap);
            }
            bitmap.recycle();
            float coordinatesForAll [][] = classifier.getAbsoluteCoordinates();
            if(coordinatesForAll.length == 0){
                return;
            }
            float coordinatesInOneLine [] = new float[coordinatesForAll.length*coordinatesForAll[0].length];

            int index = 0;
            for(int i = 0;i < coordinatesForAll.length;i++){

                coordinatesInOneLine[index++] = coordinatesForAll[i][0]*textureView.getWidth();
                coordinatesInOneLine[index++] = coordinatesForAll[i][1]*textureView.getHeight();

            }
            showText(textToShow);
            socketSendMessage(textToShow);
            drawView.setPoints(coordinatesInOneLine);
            Log.i(TAG, String.format("pose result:%s \n", textToShow));
        }

    }

    private void showText(final String text) {
        final Activity activity = getActivity();
        if (activity != null) {
            activity.runOnUiThread(
                    new Runnable() {
                        @Override
                        public void run() {
                            textView.setText(text);
                        }
                    });
        }
    }

}
